import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Scanner;

public class Main 
{
    /* Constantes de mensagens de erro */
    public final static String ERROR_COMMAND_INVALID_IN_CONTEXT = "Comando invalido no contexto actual.";
    public final static String ERROR_COMMAND_INVALID = "Comando invalido. Use o comando AJUDINHA.";
    public final static String ERROR_INVALID_COLOR = "Cor desconhecida.";
    public final static String ERROR_INVALID_PAWN = "Peao desconhecido.";
    public final static String ERROR_INVALID_NAME = "Nome invalido";
    public final static String ERROR_THROW_NOT_PERMITTED = "Lancamento nao permitido.";
    public final static String ERROR_PLAY_NOT_PERMITTED = "Jogada nao permitida.";
    public final static String ERROR_SKIP_NOT_PERMITTED = "Passagem nao permitida.";
    public final static String ERROR_START_NOT_PERMITTED = "Arranque nao permitido.";
    public final static String ERROR_NOT_PLAYER_TURN_TO_THROW = "Lancamento deve ser realizado pelo jogador ";
    public final static String ERROR_NOT_PLAYER_TURN_TO_PLAY = "Jogada deve ser realizado pelo jogador ";
    public final static String ERROR_NOT_PLAYER_TURN_TO_SKIP = "Passagem deve ser realizado pelo jogador ";
    public final static String ERROR_NOT_PLAYER_TURN_TO_START = "Arranque deve ser realizado pelo jogador ";
    public final static String ERROR_NOT_ENOUGH_PLAYERS = "Ha jogadores ainda por registar - nao e possivel iniciar o jogo.";
    public final static String ERROR_NO_SAVED_GAME = "Nao existe sessao de jogo em aberto.";
    
    /* Constantes de mensagens */
    public final static String MSG_GAME_STARTED = "Jogo iniciado.";
    public final static String MSG_GOODBYE = "Ate a proxima.";
    public final static String MSG_GAME_SAVED = "Jogo guardado.";
    public final static String MSG_GAME_RESTARTED = "Jogo reiniciado";
    public final static String MSG_DICE_THROWN = "%s obteve %d no lancamento do dado.%n";
    public final static String MSG_PLAYER_REGISTERED = "%s associado a %s.%n";
    public final static String MSG_PLAYER_HAS_FINISHED = "Jogador %s finalizou o jogo.%n";
    public final static String MSG_GAME_FOUND_FIRST_PLAYER = "Jogo pode comecar. Primeiro jogador: %s.%n";
    public final static String MSG_LEADERBOARD_HEADER = "PODIO";
    public final static String[] MSG_LEADERBOARD_POSITION = {"Vencedor: ", "Segundo lugar: ",
                                                             "Terceiro lugar: ", "Quarto lugar: "};
    public final static String MSG_LIST_PLAYERS_PAWNS_HEADER = "Jogadores";
    public final static String MSG_LIST_PLAYERS_PAWNS = "PEAO(%d, %d)%n";
    
    /* Constanstes dos comandos */
    public final static String COMMAND_QUIT = "S";
    public final static String COMMAND_HELP = "AJUDINHA";
    public final static String COMMAND_LIST_PLAYERS = "LJ";
    public final static String COMMAND_REGISTER_PLAYER = "RJ";
    public final static String COMMAND_START_GAME = "IJ";
    public final static String COMMAND_RESTART_GAME = "RIJ";
    public final static String COMMAND_INITIAL_DICE_THROW = "LID";
    public final static String COMMAND_THROW_DICE = "LD";
    public final static String COMMAND_PLAY = "JOGAR";
    public final static String COMMAND_ADD_PAWN = "AP";
    public final static String COMMAND_SKIP_TURN = "PASSAR";
    
    /* Outras constantes */
    public final static String FILE_NAME = "jogo.txt";
    public final static boolean INITIAL_THROW = true;
    
    /* Variaveis de instancia */
    private static Scanner s; // Scanner usado ao longo da classe Main

    /**
     * 
     * @param args 
     */
    public static void main(String[] args) 
    {
        s = new Scanner(System.in);
        Game myGame = new Game();
        readCommand(myGame);
        
        if ((myGame.getState() != Game.STATE_NOT_RUNNING) && 
                (myGame.getState() != Game.STATE_FIND_FIRST_PLAYER))
        {
            writeToFile(myGame);
            System.out.println(MSG_GAME_SAVED + " " + MSG_GOODBYE);
        }
        else
             System.out.println(MSG_GOODBYE);
        
        LudoGUI.deleteWindow();
        s.close();  
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void readCommand(Game myGame) 
    {
        String cmd = s.next().toUpperCase();

        while (!cmd.equals(COMMAND_QUIT)) 
        {
            if (isCmdValid(myGame, cmd, s))
            {
                switch (cmd) 
                {
                    case COMMAND_LIST_PLAYERS:
                        processLJ(myGame);
                        break;
                    case COMMAND_REGISTER_PLAYER:
                        processRJ(myGame);
                        break;
                    case COMMAND_START_GAME:
                        processIJ(myGame);
                        break;
                    case COMMAND_RESTART_GAME:
                        processRIJ(myGame);
                        break;
                    case COMMAND_INITIAL_DICE_THROW:
                        processLID(myGame);
                        break;
                    case COMMAND_THROW_DICE:
                        processLD(myGame);
                        break;
                    case COMMAND_PLAY:
                        processJogar(myGame);
                        break;
                    case COMMAND_ADD_PAWN:
                        processAP(myGame);
                        break;
                    case COMMAND_SKIP_TURN:
                        processPassar(myGame);
                        break;
                    case COMMAND_HELP:
                        processAjudinha();
                        break;
                    default:
                        System.out.println(ERROR_COMMAND_INVALID);
                }
            }
            
            cmd = s.next().toUpperCase();
        }
    }
    
    /**
     * 
     * @param myGame
     * @param cmd
     * @param s
     * @return 
     */
    private static boolean isCmdValid(Game myGame, String cmd, Scanner s)
    {
        boolean toReturn = true;
        
        if (myGame.getState() == Game.STATE_NOT_RUNNING)
        {
            switch (cmd)
            {
                case COMMAND_INITIAL_DICE_THROW:
                case COMMAND_THROW_DICE:
                case COMMAND_PLAY:
                case COMMAND_ADD_PAWN:
                case COMMAND_SKIP_TURN:
                    System.out.println(ERROR_COMMAND_INVALID_IN_CONTEXT);
                    s.nextLine();
                    toReturn = false;
            }
        }
        else
        {
            switch (cmd)
            {
                case COMMAND_REGISTER_PLAYER:
                    s.nextLine();
                case COMMAND_START_GAME: 
                case COMMAND_RESTART_GAME: 
                    System.out.println(ERROR_COMMAND_INVALID_IN_CONTEXT);
                    toReturn = false;
            }
        }
        
        return toReturn;
    }
    
    /**
     * 
     */
    private static void processAjudinha() 
    {
        System.out.println("S - Terminar programa");
        System.out.println("Ajudinha - Listagem de comandos");
        System.out.println("LJ - Lista de jogadores");
        System.out.println("RJ - Registo de jogador");
        System.out.println("IJ - Inicio de jogo");
        System.out.println("RIJ - Reinicio de jogo");
        System.out.println("LID - Lancamento de dado inicial");
        System.out.println("LD - Lancamento de dado no decorrer do jogo");
        System.out.println("JOGAR - Movimentacao de um peao");
        System.out.println("AP - Arranque de um peao");
        System.out.println("PASSAR - Passar a vez no jogo");
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void processLJ(Game myGame)
    {
        listPlayers(myGame);
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void processRJ(Game myGame) 
    {
        String inputColor = s.next().toUpperCase(); 
        String inputPlayer = s.nextLine().trim(); 
        int color = Game.processColor(inputColor);

        if (!Game.isColor(inputColor))
            System.out.println(ERROR_INVALID_COLOR);
        else if (inputPlayer.isEmpty())
            System.out.println(ERROR_INVALID_NAME);
        else
        {
            myGame.addPlayer(color, inputPlayer);
            System.out.format(MSG_PLAYER_REGISTERED, inputColor, inputPlayer);
        }   
    }
    
    
    /**
     * 
     * @param myGame 
     */
    private static void processIJ(Game myGame) 
    {
        if (myGame.hasAllPlayersRegistered())
        {
            System.out.println(MSG_GAME_STARTED);
            listPlayers(myGame); 
            myGame.startGame(); 
            LudoGUI.createWindow();
        } 
        else
            System.out.println(ERROR_NOT_ENOUGH_PLAYERS);
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void processRIJ(Game myGame)
    {
        try 
        {
            readFromFile(myGame);
            LudoGUI.createWindow();
            updatePositionsOnBoard(myGame);
            System.out.println(MSG_GAME_RESTARTED);
            listPlayers(myGame);
        }
        catch (FileNotFoundException e) 
        {
            System.out.println(ERROR_NO_SAVED_GAME);
        }
    }

    /**
     * 
     * @param myGame 
     */
    private static void processLID(Game myGame)
    {
        String inputColor = s.next().toUpperCase();
        int color = Game.processColor(inputColor);
        
        if (!Game.isColor(inputColor))
            System.out.println(ERROR_INVALID_COLOR);
        else if (myGame.getState() != Game.STATE_FIND_FIRST_PLAYER)
            System.out.println(ERROR_THROW_NOT_PERMITTED);
        else if (myGame.getCurrentPlayer() != color)
            System.out.println(ERROR_NOT_PLAYER_TURN_TO_THROW + Game.processColor(myGame.getCurrentPlayer()));
        else 
        {
            System.out.format(MSG_DICE_THROWN, inputColor, myGame.throwDice(INITIAL_THROW));
            if (myGame.getState() == Game.STATE_DICE_NOT_THROWN)
                System.out.format(MSG_GAME_FOUND_FIRST_PLAYER, Game.processColor(myGame.getCurrentPlayer()));
        }
    }

    /**
     * 
     * @param myGame 
     */
    private static void processLD(Game myGame) 
    {
        String inputColor = s.next().toUpperCase();
        int color = Game.processColor(inputColor);
        
        if (!Game.isColor(inputColor))
            System.out.println(ERROR_INVALID_COLOR);
        else if (myGame.getState() != Game.STATE_DICE_NOT_THROWN)
            System.out.println(ERROR_THROW_NOT_PERMITTED);
        else if (myGame.getCurrentPlayer() != color)
            System.out.println(ERROR_NOT_PLAYER_TURN_TO_THROW + Game.processColor(myGame.getCurrentPlayer()));
        else
            System.out.format(MSG_DICE_THROWN, inputColor, myGame.throwDice(!INITIAL_THROW));
    }

    /**
     * 
     * @param myGame 
     */
    private static void processJogar(Game myGame)
    {
        String inputColor = s.next().toUpperCase();
        int color = Game.processColor(inputColor);
        int inputPawn = s.nextInt();
        
        if (!Game.isColor(inputColor))
            System.out.println(ERROR_INVALID_COLOR);
        else if (!Game.isPawn(inputPawn))
            System.out.println(ERROR_INVALID_PAWN);
        else if (myGame.getState() != Game.STATE_DICE_THROWN)
            System.out.println(ERROR_PLAY_NOT_PERMITTED);
        else if (myGame.getCurrentPlayer() != color)
            System.out.println(ERROR_NOT_PLAYER_TURN_TO_PLAY + Game.processColor(myGame.getCurrentPlayer()));
        else
        {
            int newPosition = myGame.movePawn(inputPawn);
            
            /* TODO: Duvida, necessario este if? LudoGUI já faz a verificação caso posicao = 0 nao faz nada */
            if (newPosition != Game.INITIAL_POSITION)
                LudoGUI.pawnPosition(color, inputPawn, newPosition);
            
            listPlayersWithPawns(myGame);
            processLeaderboard(myGame, color);
        }
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void processAP(Game myGame)
    {
        String inputColor = s.next().toUpperCase();
        int color = Game.processColor(inputColor);
        int inputPawn = s.nextInt();
        
        if (!Game.isColor(inputColor))
            System.out.println(ERROR_INVALID_COLOR);
        else if (!Game.isPawn(inputPawn))
            System.out.println(ERROR_INVALID_PAWN);
        else if (myGame.getState() != Game.STATE_DICE_THROWN) 
            System.out.println(ERROR_START_NOT_PERMITTED);
        else if (myGame.getCurrentPlayer() != color) 
            System.out.println(ERROR_NOT_PLAYER_TURN_TO_START + Game.processColor(myGame.getCurrentPlayer()));
        else
        {
            /* TODO: Igual à duvida do metodo jogar */
            if (myGame.startPawn(inputPawn))
                LudoGUI.pawnPosition(color, inputPawn, Game.START_POSITION);
            
            listPlayersWithPawns(myGame);
        }
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void processPassar(Game myGame) 
    {
        String inputColor = s.next().toUpperCase();
        int color = Game.processColor(inputColor);
        
        if (!Game.isColor(inputColor))
            System.out.println(ERROR_INVALID_COLOR);
        else if (myGame.getState() != Game.STATE_DICE_THROWN)
            System.out.println(ERROR_SKIP_NOT_PERMITTED);
        else if (myGame.getCurrentPlayer() != color) 
            System.out.println(ERROR_NOT_PLAYER_TURN_TO_SKIP + Game.processColor(myGame.getCurrentPlayer()));
        else
        {
            myGame.skipTurn();
            listPlayersWithPawns(myGame);
        }        
    }
    
    /**
     * 
     * @param myGame
     * @param color 
     */
    private static void processLeaderboard(Game myGame, int color) 
    {
        if (myGame.isPlayerInLeaderboard(color)) 
        {
            System.out.format(MSG_PLAYER_HAS_FINISHED, Game.processColor(color));
            if (myGame.isLeaderboardComplete()) 
            {
                listLeaderboard(myGame);
                newGame(myGame);
            }
        }
    }
    
    /**
     * 
     * @throws IOException 
     */
    private static void createFile() throws IOException
    {
        File f = new File(FILE_NAME);
        
        if (f.exists()) 
            f.delete();
            
        f.createNewFile();
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void writeToFile(Game myGame)
    {
        try (PrintWriter pw = new PrintWriter(FILE_NAME)) 
        {
            createFile();
            
            /* Inicio da escrita do ficheiro de jogo */
            
            pw.println(myGame.getState());
            pw.println(myGame.getCurrentPlayer());
            pw.println(myGame.getDiceNumber());
            
            for (int i = 1; i <= Game.MAX_PLAYERS; i++)
            {
                pw.println(myGame.getPlayer(i).getName());
                pw.println(myGame.getPlayer(i).getPawnFinishCount());
                
                for (int x = 1; x <= Game.MAX_PAWNS; x++)
                    pw.println(myGame.getPlayer(i).getPawn(x).getPosition());
            }
            
            pw.println(myGame.getCounterLeaderboard());

            for (int i = 0; i < myGame.getCounterLeaderboard(); i++) 
                pw.println(myGame.getPlayerFromLeaderboard(i).getColor());
            
            /* Fim da escrita do ficheiro de jogo */
        }
        catch (IOException e)
        {
            System.out.println("Erro: " + e.getMessage());
        }
    }
    
    /**
     * 
     * @param myGame
     * @throws FileNotFoundException 
     */
    private static void readFromFile(Game myGame) throws FileNotFoundException 
    {
        try (FileReader fr = new FileReader(FILE_NAME); Scanner sc = new Scanner(fr)) 
        {
            myGame.setState(Integer.parseInt(sc.nextLine()));
            myGame.setCurrentPlayer(Integer.parseInt(sc.nextLine()));
            myGame.setDiceNumber(Integer.parseInt(sc.nextLine()));
            
            for (int i = 1; i <= Game.MAX_PLAYERS; i++)
            {
                readPlayer(i, myGame, sc);
                for (int x = 1; x <= Game.MAX_PAWNS; x++)
                    readPawn(i, x, myGame, sc);
            }
            
            readLeaderboard(myGame, sc);
        }
        catch (IOException e)
        {
            System.out.println("Erro: " + e.getMessage());
        }
    }
    
    /**
     * 
     * @param color
     * @param myGame
     * @param sc 
     */
    private static void readPlayer(int color, Game myGame, Scanner sc)
    {
        String name = sc.nextLine();
        int pawnFinishCount = Integer.parseInt(sc.nextLine());
        
        myGame.addPlayer(color, name);
        myGame.getPlayer(color).setPawnFinishCount(pawnFinishCount);
    }
    
    /**
     * 
     * @param color
     * @param pawnId
     * @param myGame
     * @param sc 
     */
    private static void readPawn(int color, int pawnId, Game myGame, Scanner sc)
    {
        int position = Integer.parseInt(sc.nextLine());

        myGame.getPlayer(color).getPawn(pawnId).setPosition(position);
    }
    
    /**
     * 
     * @param myGame
     * @param sc 
     */
    private static void readLeaderboard(Game myGame, Scanner sc)
    {
        int numberOfEntries = Integer.parseInt(sc.nextLine());
        
        for (int i = 0; i < numberOfEntries; i++)
        {
            int color = Integer.parseInt(sc.nextLine());
            myGame.addPlayerToLeaderboard(color);
        }
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void updatePositionsOnBoard(Game myGame)
    {
        /* TODO: Necessario fazer skip as posicoes 0? */
        for (int i = 1; i <= Game.MAX_PLAYERS; i++ )
            for (int p = 1; p <= Game.MAX_PAWNS; p++)
               LudoGUI.pawnPosition(i, p, myGame.getPlayer(i).getPawn(p).getPosition());
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void newGame(Game myGame)
    {
        myGame.newGame();
        LudoGUI.deleteWindow();
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void listPlayers(Game myGame)
    {
        for (int i = 1 ; i <= Game.MAX_PLAYERS ; i++)
            System.out.println(Game.processColor(i) + ": " + myGame.getPlayer(i).getName());
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void listPlayersWithPawns(Game myGame)
    {
        System.out.println(MSG_LIST_PLAYERS_PAWNS_HEADER);
        for (int i = 1; i <= Game.MAX_PLAYERS; i++ )
        {
            System.out.println(Game.processColor(i) + ":");
            for (int p = 1; p <= Game.MAX_PAWNS; p++)
               System.out.format(MSG_LIST_PLAYERS_PAWNS, p, myGame.getPlayer(i).getPawn(p).getPosition());
        }
    }
    
    /**
     * 
     * @param myGame 
     */
    private static void listLeaderboard(Game myGame)
    {
        System.out.println(MSG_LEADERBOARD_HEADER);
        for (int i = 0; i < Game.LENGHT_LEADERBOARD; i++)
        {
            System.out.println(MSG_LEADERBOARD_POSITION[i] + 
                    Game.processColor(myGame.getPlayerFromLeaderboard(i).getColor()) + " " +
                    myGame.getPlayerFromLeaderboard(i).getName());
        }
    }
}